#include "Renderer.h"


Renderer::Renderer(){
	//printf("Created a renderer\n");
	init();
}


Renderer::~Renderer(){
	//delete the ImageBuffer
	if (autoImageBuffer){
		delete theImage;
	}
}

void Renderer::init(){
	theAAmode = 0;			//no anti-aliasing
	samplePatternMode = 0;	//grid pattern
	theAAminSamples = 1;	//1 AA sample per pixel
	theAAmaxSamples = 1;	//maximum 1 sample per pixel
	readyToRender = false;

	theScene = NULL;
	theImage = NULL;
	theCamera = NULL;

	autoImageBuffer = false;
	
	backgroundCol = vec4(0,0,0,0);

	theMaxBounces = 4;

	clampingEnabled = true;
}

bool Renderer::prepare(){
	readyToRender = false;

	//if there's no camera
	if (theCamera == NULL){
		//you're not ready to render
		printf("No camera found\n");
		return false;
	}
	//calculate these values
	//these values will be used every time renderPixel is called
	h_tanFov = tan(theCamera->h_fov() * (float)M_PI / 180.0f);
	v_tanFov = tan(theCamera->v_fov() * (float)M_PI / 180.0f);
	x_pixWidth = 1.0f / theCamera->scWidth();
	y_pixWidth = 1.0f / theCamera->scHeight();
	forward = normalize(theCamera->target() - theCamera->position());
	right = normalize(cross(forward, theCamera->up()));
	up = normalize(cross(right, forward));

	//if there's no scene to render
	if (theScene == NULL){
		printf("No Scene found\n");
		return false;
	}
	//if there's no image buffer
	if (theImage == NULL){
		printf("No Image found\n");
		//create an image buffer
		theImage = new ImageBuffer(theCamera->scWidth(), theCamera->scHeight());
		autoImageBuffer = true;
	}
	//if the image buffer's dimensions don't match those of the camera
	if (theImage->width() != theCamera->scWidth() || theImage->height() != theCamera->scHeight()){
		//resize the image buffer
		theImage->resize(theCamera->scWidth(), theCamera->scHeight());
	}
	//make sure the image has enough layers
	if (theAAmode == 2){	//adaptive anti-aliasing needs two layers
		theImage->layers(2);
	}
	//clear the image buffer
	theImage->clear();
	//build a BVH from the scene elements for fast intersection tests
	printf("Building BVH: ");
	clock_t t;
	t = clock();
	bool bvhBuilt = theBVH.build(theScene, 1);
	t = clock() - t;
	printf("%d clicks (%f seconds).\n", t, ((float)t) / CLOCKS_PER_SEC);
	if (!bvhBuilt){
		printf("BVH build failed somewhere\n");
		return false;
	}
	theScene->theBVH(&theBVH);	//set the Scene's pointer to the BVH
	//return false;
	readyToRender = true;
	return true;
}

void Renderer::render(){
	if (!readyToRender){
		//echo some error message
		printf("You're not ready to render\n");
		return;
	}

	//render the scene!!!
	switch (theAAmode){
	case 1:			//normal anti-aliasing
		renderUniformAA(theAAminSamples);
		break;
	case 2:			//egde anti-aliasing
		renderDynamicAA(theAAminSamples, theAAmaxSamples);
		break;
	case 0:			//no anti-aliasing
	default:
		renderUniformAA(1);
		break;
	}
}

void Renderer::renderUniformAA(int _samples){
	int imgWidth = theCamera->scWidth();
	int imgHeight = theCamera->scHeight();
	//for each pixel
	for (int y = 0; y < imgHeight; y++){
		for (int x = 0; x < imgWidth; x++){
			//render the pixel with the number of samples
			renderPixel(x, y, _samples);
		}
	}
		
}

void Renderer::renderDynamicAA(int _minSamples, int _maxSamples){
	//render each pixel with min AA samples
	renderUniformAA(_minSamples);
	//calculate the image gradient
	theImage->edgeDetect(0, 1);	//take the active layer and edge detect it into the second layer
	theImage->toGrey(1, 1, 1);	//get the high values at each pixel and choose that as the grey value

	int imgWidth = theCamera->scWidth();
	int imgHeight = theCamera->scHeight();
	//discretize the gradient values into equally-sized ranges.
	//the number of ranges is equal to AAmaxSamples - AAminSamples + 1
	vec2 imgRangeVals = theImage->minMaxVals(1);
	float imgRange = imgRangeVals[1] - imgRangeVals[0];
	int partitionCount = _maxSamples - _minSamples + 1;
	float partitionSize = imgRange / (float)partitionCount;
	
	//for each pixel
	for (int y = 0; y < imgHeight; y++){
		for (int x = 0; x < imgWidth; x++){
			//if the required number of final samples for this pixel > AAminSamples
			int requiredSamples = _minSamples;
			for (int i = 1; i < partitionCount; i++){
				//re-render the pixel with the required number of samples
				theImage->activeLayer(1);	//get the value from the edge detect layer
				float rangeBottom = imgRangeVals[0] + i * partitionSize;
				float rangeTop = rangeBottom + partitionSize;
				float pixelVal = theImage->pixel(x, y).r;
				theImage->activeLayer(0);	//set it back to the render layer
				if (pixelVal >= rangeBottom && pixelVal < rangeTop){
					//render the pixel with minSamples + i
					renderPixel(x, y, i + _minSamples);
					//break out of the loop
					break;
				}
			}
				
		}
	}
		
}

void Renderer::renderPixel(int _x, int _y, int _samples){
	//printf("Rendering pixel (%i, %i)\n", _x, _y);
	//float h_fov = theCamera->h_fov();
	//float v_fov = theCamera->v_fov();
	
	float x0to1 = (float)_x / theCamera->scWidth();
	float y0to1 = (float)_y / theCamera->scHeight();

	vec4 avgCol = vec4(0, 0, 0, 0);

	//vec3 forward = normalize(theCamera->target() - theCamera->position());
	//vec3 right = normalize(cross(forward, theCamera->up()));
	//vec3 up = normalize(cross(right, forward));

	//float h_tanFov = tan(h_fov * (float)M_PI / 180.0f);	//TODO: calculate and store these variables before renderPixel is ever called. This will avoid a lot of expensive calls to tan
	//float v_tanFov = tan(v_fov * (float)M_PI / 180.0f);
	
	//vec3 origin = theCamera->position();

	//float x_pixWidth = 1.0f / theCamera->scWidth();
	//float y_pixWidth = 1.0f / theCamera->scHeight();
	for (int sx = 0; sx < _samples; sx++){
		for (int sy = 0; sy < _samples; sy++){
			float sub_x0to1 = x0to1 + ((float)sx / (float)_samples) * x_pixWidth;
			float sub_y0to1 = y0to1 + ((float)sy / (float)_samples) * y_pixWidth;
			vec3 rayDir = forward + right * ((sub_x0to1 - 0.5f) * h_tanFov) + up * ((sub_y0to1 - 0.5f) * v_tanFov);	//transform the ray into world space
			rayDir = normalize(rayDir);	//is this necessary?
			Ray *theRay = new Ray(theCamera->position(), rayDir, theMaxBounces, 0, 0);	//start at a depth of 0

			//theRay->toString();

			//create an intersection object
			//this object contains the ray, the closest intersection, and the distance to the object
			Intersection _int = Intersection(theRay);

			//fire it into the BVH instead
			theBVH.intersect(_int);
			
			/*//THE OLD WAY
			//find the closest object that the ray hits
			vector<Shape*>::iterator nextShape = theScene->shapes();
			int shapeCount = theScene->shapeCount();
			for (int i = 0; i < shapeCount; i++){
				//printf("Checking this shape for an intersection\n");
				Shape *theNext = *nextShape;
				theNext->intersect(_int);	//perform the intersection calculation
				nextShape++;
			}
			//END THE OLD WAY*/

			//if the ray hit something
			if (_int.intersected()){
				//calculate the position and surface normal at that intersection
				//call that object's getColor function
				Shape *theClosest = _int.shape();
				avgCol += theClosest->getColor(_int);
				//avgCol = vec4(_int.surfNorm(), 1);
				//printf("The color: (%f, %f, %f)\n", avgCol.r, avgCol.g, avgCol.b);
			}
			else{
				//draw the background color
				avgCol += backgroundCol;
			}
			delete theRay;
		}
	}
	avgCol /= _samples * _samples;		//average the results
	//clamp the color
	if (clampingEnabled){
		avgCol = clamp(avgCol, 0.0f, 1.0f);
	}
	theImage->pixel(_x, _y, avgCol);	//set the pixel color
}

void Renderer::image(ImageBuffer* _im){
	//delete the existing one if any and if it was generated automatically
	if (theImage != NULL && autoImageBuffer){
		delete theImage;
	}
	theImage = _im;
}

void Renderer::debugPixel(int _x, int _y){
	printf("Debugging pixel (%d, %d)\n", _x, _y);
	int i;
	i = 0;

	renderPixel(_x, _y, 1);
	printf("Done\n");
}
